package com.zhou.framework.controller;

import com.alibaba.fastjson.JSON;
import com.zhou.framework.aop.enums.LogLevel;
import com.zhou.framework.dao.Log;
import com.zhou.framework.exception.*;
import com.zhou.framework.jwt.model.LoginUser;
import com.zhou.framework.model.RS;
import com.zhou.framework.util.ExceptionUtil;
import com.zhou.framework.util.SessionUtil;
import com.zhou.framework.util.ThreadPoolManager;
import com.zhou.org.common.MenuCache;
import com.zhou.org.dao.User;
import com.zhou.util.StringTool;
import com.zhou.util.WebUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author lang.zhou
 * @date 2022/4/29 16:59
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {



    @ExceptionHandler(value = Exception.class)
    public RS handlerException(HttpServletRequest req, Exception e) {
        log.error("",e);
        return RS.failed("服务器发生错误",500);
    }

    private void saveLog(HttpServletRequest req, Exception e){
        Log operatorLog = this.createLog(req);
        saveLog(req, e,operatorLog);
    }
    private void saveLog(HttpServletRequest req, Exception e,Log operatorLog){

        // 获取当前的用户
        LoginUser loginUser = SessionUtil.getCurrentUserSafe();
        User user;
        if(loginUser != null){
            user = loginUser.getUser();
            operatorLog.setUserId(user.getUserId());
            operatorLog.setUsername(user.getRealName());
        }
        operatorLog.setStatus(ExceptionUtil.getExceptionName(e));
        if(StringTool.isBlank(operatorLog.getErrorMsg())){
            operatorLog.setErrorMsg(StringTool.substring(e.getMessage(), 0, 2000));
        }
        String params = JSON.toJSONString(WebUtil.getParameterMap(req));
        operatorLog.setParameter(StringTool.substring(params, 0, 2000));
        ThreadPoolManager.saveLog(operatorLog);
    }

    private Log createLog(HttpServletRequest request){
        Log l = new Log();
        String requestType = request.getHeader("X-Requested-With");
        l.setType("XMLHttpRequest".equals(requestType) ? 1 : 0);

        l.setCreateDt(new Date());
        l.setSpendTime(0L);
        l.setLogLevel(LogLevel.ERROR.value);
        l.setUrl(StringTool.substring(request.getRequestURL().toString(),0,500));
        l.setIp(WebUtil.getIp(request));
        l.setOs(StringTool.substring(WebUtil.getOsInfo(request),0,400));
        l.setBrowser(StringTool.substring(WebUtil.getBrowserInfo(request),0,400));
        l.setMethod(request.getMethod());
        l.setDescription(MenuCache.getUrlName(request));

        return l;
    }

    /**
     * 参数验证异常
     */
    @ExceptionHandler(value = VerifyException.class)
    public RS verifyException(HttpServletRequest req, VerifyException e) {
        log.error(e.getMessage());
        this.saveLog(req,e);
        return RS.failed(e.getMessage(),400);
    }

    /**
     * 验证码错误
     */
    @ExceptionHandler(value = CaptchaException.class)
    public RS captchaException(HttpServletRequest req, CaptchaException e) {
        return RS.failed("验证码错误",500);
    }
    /**
     * 请求频率异常
     */
    @ExceptionHandler(value = RateLimiterException.class)
    public RS verifyRateLimiterException(HttpServletRequest req, RateLimiterException e) {
        log.error(e.getMessage());
        this.saveLog(req,e);
        return RS.failed("网络繁忙，请稍候再试",400);
    }

    /**
     * 锁异常
     */
    @ExceptionHandler(value = LockException.class)
    public RS lockException(HttpServletRequest req, LockException e) {
        log.error(e.getMessage());
        //this.saveLog(req,e);
        return RS.failed("请求过于频繁",400);
    }

    /**
     * 权限异常
     */
    @ExceptionHandler(value = AuthException.class)
    public RS authException(HttpServletRequest req, AuthException e) {
        log.error(e.getMessage());
        this.saveLog(req,e);
        return RS.failed(e.getMessage(),403);
    }

    /**
     * token验证异常
     */
    @ExceptionHandler(value = TokenException.class)
    public RS tokenException(HttpServletRequest req, TokenException e) {
        log.error(e.getMessage());
        return RS.failed(e.getMessage(),401);
    }

    /**
     * 会话过期
     */
    @ExceptionHandler(value = SessionOutTimeException.class)
    public RS sessionOutTimeException(HttpServletRequest req, SessionOutTimeException e) {
        log.error("会话超时");
        RS r = RS.failed(e.getMessage(), 401);
        r.put("timeout",true);
        return r;
    }

    /**
     * 上传控件异常
     */
    @ExceptionHandler(value = FileUploadException.class)
    public RS fileUploadException(HttpServletRequest req, FileUploadException e) {
        log.error("文件上传失败");
        this.saveLog(req,e);
        return RS.failed(e.getMessage(), 500);
    }

    /**
     * 登录验证异常
     */
    @ExceptionHandler(value = LoginException.class)
    public RS loginException(HttpServletRequest req, LoginException e) {
        log.error(e.getMessage());
        return RS.failed(e.getMessage(),403);
    }

    private RS handleValidate(HttpServletRequest req, Exception e, Set<String> message,int code){
        String join = StringUtils.join(message, "，");
        Log operatorLog = this.createLog(req);
        operatorLog.setErrorMsg(join);
        this.saveLog(req,e,operatorLog);
        return RS.failed(join,code);
    }
    /**
     * request参数验证异常
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public RS paramException(HttpServletRequest req, MethodArgumentNotValidException e) {
        log.error(e.getMessage());
        BindingResult result = e.getBindingResult();
        Set<String> message = new LinkedHashSet<>();
        for (FieldError item : result.getFieldErrors()) {
            String itemMessage = item.getDefaultMessage();
            //item.getField()
            message.add(itemMessage);
        }
        return handleValidate(req,e,message,400);
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    public RS handleConstraintViolationException(HttpServletRequest req,ConstraintViolationException ex) {
        log.error(ex.getMessage());
        Set<String> message = new LinkedHashSet<>();
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        if (!CollectionUtils.isEmpty(constraintViolations)) {
            constraintViolations.forEach(v -> message.add(v.getMessage()));
        }
        return handleValidate(req,ex,message,400);
    }
    @ExceptionHandler(value = BindException.class)
    public RS handleBindException(HttpServletRequest req, BindException ex) {
        log.error(ex.getMessage());
        Set<String> message = new LinkedHashSet<>();

        BindingResult result = ex.getBindingResult();
        for (ObjectError error : result.getAllErrors()) {
            message.add(error.getDefaultMessage());
        }
        return handleValidate(req,ex,message,400);
    }

    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public RS handleHttpRequestMethodNotSupportedException(HttpServletRequest req, HttpRequestMethodNotSupportedException ex) {
        log.error(ex.getMessage());
        Log operatorLog = this.createLog(req);
        operatorLog.setErrorMsg("方法错误");
        this.saveLog(req,ex,operatorLog);
        return RS.failed(operatorLog.getErrorMsg(),400);
    }
    @ExceptionHandler(value = {MissingServletRequestPartException.class, HttpMediaTypeNotSupportedException.class, MultipartException.class
            ,HttpMessageNotReadableException.class, MissingServletRequestParameterException.class})
    public RS handleMissingServletRequestPartException(HttpServletRequest req, Exception ex) {
        log.error(ex.getMessage());
        Log operatorLog = this.createLog(req);
        operatorLog.setErrorMsg("参数不正确");
        this.saveLog(req,ex,operatorLog);
        return RS.failed(operatorLog.getErrorMsg(),400);
    }

    /*@ExceptionHandler(MultipartException.class)
    public void fileUploadExceptionHandler(MultipartException exception, HttpServletResponse response)  throws IOException {
        int maxSize = 100;//NumberUtil.safeToInteger(SettingUtil.getIntValue("UPLOADER","max-file-size"),100);
        if (exception.getCause().getCause() instanceof FileUploadBase.FileSizeLimitExceededException) {
            response.setHeader("file-status","1");
        } else if (exception.getCause().getCause() instanceof FileUploadBase.SizeLimitExceededException) {
            response.setHeader("file-status","1");
        }
    }*/
}
